(function(root){
    //监听对象
    var suber = null;
    function observe(data){
        if(!data || typeof data !== 'object') return;
        Object.keys(data).forEach(function(key){
            defineReactive(data, key, data[key]);
        })
    }
    function defineReactive(object, key, val){
        observe(val);
        var dep = {
            subs: [],
            update: function(){
                this.subs.forEach(function(sub){
                    sub.update();
                })
            }
        }
        Object.defineProperty(object, key, {
            configurable: true,
            enumerable: true,
            get: function(){
                suber && dep.subs.push(suber);
                return val;
            },
            set: function(newVal){
                val = newVal;
                dep.update();
            }
        })
    }

    //订阅者
    function Watcher(vm, exp, cb){
        this.vm = vm;
        this.exp = exp;
        this.cb = cb;
        suber = this;
        this.value = vm[exp];
        suber = null;
    }
    Watcher.prototype = {
        update: function(){
            var newVal = this.vm[this.exp];
            if(newVal !== this.value){
                this.cb.call(this.vm, newVal);
                this.value = newVal;
            }
        }
    }

    //解析模板
    function Compile(el, vm){
        this.el = document.querySelector(el);
        this.vm = vm;
        this.fragment = "";
        this.init();
    }
    Compile.prototype = {
        init: function(){
            this.fragment = this.getFragment();
            this.compile(this.fragment);
            this.el.appendChild(this.fragment);
        },
        getFragment: function(){
            var fragment = document.createDocumentFragment();
            var child = this.el.firstChild;
            while(child){
                fragment.appendChild(child);
                child = this.el.firstChild;
            } 
            return fragment;
        },
        compile: function(html){
            var reg = /\{\{(.*)\}\}/;
            Array.prototype.slice.call(html.childNodes).forEach(function(node){
                var nodeText = node.textContent;
                if(this.isTextNode(node) && reg.test(nodeText)){
                    this.compileText(node, reg.exec(nodeText)[1]);
                }else if(this.isElementNode(node)){
                    this.compileElement(node);
                }
                if (node.childNodes && node.childNodes.length) {
                    this.compile(node);
                }
            }.bind(this));
        },
        compileElement: function(node){
            var attributes = node.attributes;
            Array.prototype.slice.call(attributes).forEach(function(attribute){
                var key = attribute.name;
                var val = attribute.value;
                if(this.isDirective(key)){
                    if(this.isModelDirective(key)){
                        this.compileModel(node, val);
                    }else if(this.isEventDirective(key)){
                        this.compileEvent(node, key, val);
                    }
                }
            }.bind(this))
        },
        compileModel: function(node, exp){
            var self = this;
            this.renderVal(node, this.vm[exp]);
            new Watcher(self.vm, exp, function(value){
                self.renderVal(node, value);
            })
            node.addEventListener('keyup', function(){
                self.vm[exp] = node.value;
            })
        },
        compileEvent: function(node, key, val){
            var event = key.slice(5);
            var exp = val.split('(')[0];
            var cb = this.vm.methods[exp];
            node.addEventListener(event, function(){
                cb.call(this.vm);
            }.bind(this), false);
        },
        isDirective: function(key){
            return key.indexOf('v-') === 0;
        },
        isModelDirective: function(key){
            return key.indexOf('v-model') === 0;
        },
        isEventDirective: function(key){
            return key.indexOf('v-on:') === 0;
        },
        isTextNode: function(node){
            return node.nodeType === 3;
        },
        isElementNode: function(node){
            return node.nodeType === 1;
        },
        compileText: function(node, exp){
            var self = this;
            this.renderText(node, this.vm[exp]);
            new Watcher(self.vm, exp, function(value){
                self.renderText(node, value);
            })
        },
        renderText: function(node, value){
            node.textContent = value;
        },
        renderVal: function(node, value){
            node.value = value;
        }
    }


    function Vue (options) {
        var self = this;
        this.data = options.data;
        this.methods = options.methods;

        Object.keys(this.data).forEach(function(key) {
            self.proxyKeys(key);
        });

        observe(this.data);
        new Compile(options.el, this);
        options.mounted && options.mounted.call(this);
    }

    Vue.prototype = {
        proxyKeys: function (key) {
            var self = this;
            Object.defineProperty(this, key, {
                enumerable: false,
                configurable: true,
                get: function getter () {
                    return self.data[key];
                },
                set: function setter (newVal) {
                    self.data[key] = newVal;
                }
            });
        }
    }

    window.Vue = Vue;
})(window)